package com.meego.common.crypto;

import com.meego.common.crypto.KeyStore.KeyStoreType;
import com.meego.common.log.GA;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;

@SuppressWarnings("unchecked")
public class CryptoKey {

    private static CryptoKey sInstance;
    private HashMap<KeyStoreType, KeyStore> keyStoreMap = new HashMap();
    private HashMap<KeyStoreType, Map> keyStoreCache = new HashMap();

    public static CryptoKey getInstance() {
        if (sInstance == null) {
            sInstance = new CryptoKey();
        }
        return sInstance;
    }

    public CryptoKey() {
        KeyStore rsaKeyStore = new KeyStore();
        Map rsaKeyStoreCache = Collections.synchronizedMap(new HashMap());
        this.keyStoreMap.put(KeyStoreType.STK, rsaKeyStore);
        this.keyStoreCache.put(KeyStoreType.STK, rsaKeyStoreCache);
    }

    public CryptoKey(KeyStore rsaKeyStore) {
        Map rsaKeyStoreCache = Collections.synchronizedMap(new HashMap());
        this.keyStoreMap.put(KeyStoreType.STK, rsaKeyStore);
        this.keyStoreCache.put(KeyStoreType.STK, rsaKeyStoreCache);
    }

    private BigInteger load(KeyStoreType keyStoreType, String prefix, String elementName) {
        KeyStore licenceKeyStore = (KeyStore) this.keyStoreMap.get(keyStoreType);
        byte[] bits = licenceKeyStore.getKey(new StringBuilder().append(prefix).append(elementName).toString());
        if (bits == null) {
            return null;
        }

        return new BigInteger(1, bits);
    }

    public RSAKeyParameters getRsaPublicKey(KeyStoreType keyStoreType, int index) {
        return getKey(keyStoreType, index, false);
    }

    public RSAKeyParameters getRsaPrivateKey(KeyStoreType keyStoreType, int index) {
        return getKey(keyStoreType, index, true);
    }

    public String getDesKey(KeyStoreType keyStoreType, int index) throws Exception {
        return getKey(keyStoreType, index);
    }

    public PublicKey getRsaPublicKey(RSAKeyParameters params) throws Exception {
        return getRsaPublickkey(params.getExponent(), params.getModulus());
    }

    public PrivateKey getRsaPrivatKey(RSAKeyParameters params) throws Exception {
        return getRsaPrivatekkey(params.getExponent(), params.getModulus());
    }

    public PublicKey getRsaPublickkey(BigInteger exponent, BigInteger modulus) throws Exception {
        java.security.spec.RSAPublicKeySpec publicKeySpec =
                new java.security.spec.RSAPublicKeySpec(modulus, exponent);
        KeyFactory keyFactory;
        try {
            keyFactory = KeyFactory.getInstance("RSA");

            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
            return publicKey;
        } catch (Exception e) {
            throw e;
        }
    }

    public PrivateKey getRsaPrivatekkey(BigInteger exponent, BigInteger modulus) throws Exception {
        java.security.spec.RSAPrivateKeySpec publicKeySpec =
                new java.security.spec.RSAPrivateKeySpec(modulus, exponent);
        KeyFactory keyFactory;
        try {
            keyFactory = KeyFactory.getInstance("RSA");

            PrivateKey publicKey = keyFactory.generatePrivate(publicKeySpec);
            return publicKey;
        } catch (Exception e) {
            throw e;
        }
    }

    private RSAKeyParameters getKey(KeyStoreType keyStoreType, int index, boolean isPrivateKey) {
        String prefix = new StringBuilder().append(keyStoreType.toString().toLowerCase()).append(".").append(index).toString();
        String hash_prefix = new StringBuilder().append(prefix).append(isPrivateKey ? ".private" : ".public").toString();
        Map cache = (Map) this.keyStoreCache.get(keyStoreType);
        if (cache.containsKey(hash_prefix)) {
            return (RSAKeyParameters) cache.get(hash_prefix);
        }
        RSAKeyParameters result = null;

        BigInteger modulus = load(keyStoreType, prefix, ".modulus");
        BigInteger pub = load(keyStoreType, prefix, ".pub");

        if ((modulus != null) && (pub != null)) {
            if (isPrivateKey) {
                result = new RSAPrivateCrtKeyParameters(modulus, pub, load(
                        keyStoreType, prefix, ".private"), load(keyStoreType,
                        prefix, ".p"), load(keyStoreType, prefix, ".q"), load(
                        keyStoreType, prefix, ".dP"), load(keyStoreType,
                        prefix, ".dQ"), load(keyStoreType, prefix, ".qInv"));
            } else {
                result = new RSAKeyParameters(false, modulus, pub);
            }
        }
        cache.put(hash_prefix, result);
        return result;
    }

    private String getKey(KeyStoreType keyStoreType, int index) throws Exception {
        String prefix = new StringBuilder().append(keyStoreType.toString().toLowerCase()).append(".").append(index).toString();
        String hash_prefix = new StringBuilder().append(prefix).append(".tripledes").toString();
        Map cache = (Map) this.keyStoreCache.get(keyStoreType);
        if (cache.containsKey(hash_prefix)) {
            return (String) cache.get(hash_prefix);
        }
        String result;

        BigInteger df = load(keyStoreType, prefix, ".df");
        BigInteger iv = load(keyStoreType, prefix, ".iv");
        if (df == null || iv == null) {
            InvalidKeyException e = new InvalidKeyException("Invalid Key Exception", "3DES");
            GA.crypto.error("Can not load TripleDes key", e);
            throw e;
        }
        result = new String(df.toByteArray()) + new String(iv.toByteArray());
        cache.put(hash_prefix, result);
        return result;
    }
}
